﻿using DotNetCommon.Extensions;
using DotNetCommon.Logger;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace DotNetCommon;

/// <summary>
/// 创建防抖函数的模式, 详见 <seealso cref="DebounceUtil.Create(Func{Task}, TimeSpan, bool, EnumDebounceWindowMode)"/>
/// </summary>
public enum EnumDebounceWindowMode
{
    /// <summary>
    /// 间隔内重复触发, 相对上一次执行的时间延迟一个间隔
    /// </summary>
    DelayRelativeLastExe,
    /// <summary>
    /// 间隔内重复触发, 相对现在延迟一个间隔
    /// </summary>
    DelayRelativeNow,
    /// <summary>
    /// 间隔内重复触发, 直接丢弃
    /// </summary>
    Discard
}

/// <summary>
/// 防抖函数生成器
/// </summary>
public static class DebounceUtil
{
    private static readonly ILogger logger = LoggerFactory.CreateLogger("DotNetCommon.DebounceUtil");
    /// <summary>
    /// 创建一个防抖函数
    /// </summary>
    /// <param name="act">实际代码</param>
    /// <param name="windowTime">执行间隔</param>
    /// <param name="isFirstExe">首次是否直接运行</param>
    /// <param name="windowMode">间隔内重复触发的处理策略</param>
    public static Func<Task> Create(Func<Task> act, TimeSpan windowTime, bool isFirstExe, EnumDebounceWindowMode windowMode)
    {
        CancellationTokenSource lastToken = null;//上次的取消令牌
        DateTime? zeroTime = null;//延迟计时的起始时间
        var funcKey = $"{DateTime.Now.ToCommonStamp2String()}#{Guid.NewGuid():N}";
        async Task catchAct()
        {
            try
            {
                await act();
            }
            catch (Exception ex)
            {
                logger.LogError(ex, $"执行防抖函数报错[funcKey={funcKey}]: {ex.Message}");
            }
        }
        return async () =>
        {
            await AsyncLocker.LockAsync(funcKey, async () =>
            {
                var now = DateTime.Now;
                //先将上次的取消掉
                lastToken?.Cancel();
                lastToken = null;

                //本次是否运行了
                var isThisRun = false;

                if (zeroTime == null)
                {
                    //首次触发
                    zeroTime = now;//不管首次是否执行, 都要记录时间
                    if (isFirstExe)
                    {
                        isThisRun = true;
                        Console.WriteLine($"{now.ToCommonStampString()} 首次");
                        await catchAct();
                    }
                }

                if (!isThisRun)
                {
                    //现在是否能执行                    
                    if (now - zeroTime.Value >= windowTime)
                    {
                        //可以执行
                        isThisRun = true;
                        Console.WriteLine($"{now.ToCommonStampString()} 可以执行");
                        zeroTime = now;
                        await catchAct();
                    }
                }

                //如果已经执行了, 则不再执行
                if (isThisRun) return;

                //直接丢弃
                if (windowMode == EnumDebounceWindowMode.Discard) return;

                //设置延迟执行
                var token = lastToken = new CancellationTokenSource();
                TimeSpan thisDelay;
                if (windowMode == EnumDebounceWindowMode.DelayRelativeNow)
                {
                    //相对现在延迟
                    thisDelay = windowTime;
                    zeroTime = now;//起点时间需要偏移到现在
                }
                else
                {
                    //相对上次执行延迟
                    thisDelay = windowTime - (now - zeroTime.Value);
                }
                _ = Task.Delay(thisDelay).ContinueWith(async t =>
                {
                    if (token.IsCancellationRequested) return;
                    zeroTime = DateTime.Now;
                    await catchAct();
                    return;
                });
            });
        };
    }

    #region CreateLikeInput
    /// <summary>
    /// 类似html中input的 oninput 事件, 需要等一个 windowTime 间隔后才执行(间隔内不能有触发动作), 解释:<br /><br />
    /// 假设设定间隔为 1s, 外部触发调用的时间点为: 0s 0.4s 1s<br />
    /// 则最终只会在 2s 的时候执行一次<br/><br/>
    /// 示例:
    /// <code>
    /// var recommends = new List&lt;string>();
    /// Func&lt;string, Task> handleInput = DebounceUtil.CreateLikeInput&lt;string>(async (str) =>
    /// {
    ///     //远程查询提示词
    ///     recommends = await Task.FromResult(new List&lt;string> { str, "hello, " + str });
    /// }, TimeSpan.FromSeconds(1));
    /// </code>
    /// </summary>
    public static Func<Task> CreateLikeInput(Func<Task> act, TimeSpan windowTime)
    {
        return Create(act, windowTime, false, EnumDebounceWindowMode.DelayRelativeNow);
    }

    /// <inheritdoc cref="CreateLikeInput(Func{Task}, TimeSpan)" />
    public static Func<T, Task> CreateLikeInput<T>(Func<T, Task> act, TimeSpan windowTime)
    {
        T para = default;
        var act2 = Create(() => act(para), windowTime, false, EnumDebounceWindowMode.DelayRelativeNow);
        return async (T t) =>
        {
            para = t;
            await act2();
        };
    }

    /// <inheritdoc cref="CreateLikeInput(Func{Task}, TimeSpan)" />
    public static Func<T, T2, Task> CreateLikeInput<T, T2>(Func<T, T2, Task> act, TimeSpan windowTime)
    {
        T para = default;
        T2 para2 = default;
        var act2 = Create(() => act(para, para2), windowTime, false, EnumDebounceWindowMode.DelayRelativeNow);
        return async (T t, T2 t2) =>
        {
            para = t;
            para2 = t2;
            await act2();
        };
    }

    /// <inheritdoc cref="CreateLikeInput(Func{Task}, TimeSpan)" />
    public static Func<T, T2, T3, Task> CreateLikeInput<T, T2, T3>(Func<T, T2, T3, Task> act, TimeSpan windowTime)
    {
        T para = default;
        T2 para2 = default;
        T3 para3 = default;
        var act2 = Create(() => act(para, para2, para3), windowTime, false, EnumDebounceWindowMode.DelayRelativeNow);
        return async (T t, T2 t2, T3 t3) =>
        {
            para = t;
            para2 = t2;
            para3 = t3;
            await act2();
        };
    }

    /// <inheritdoc cref="CreateLikeInput(Func{Task}, TimeSpan)" />
    public static Func<T, T2, T3, T4, Task> CreateLikeInput<T, T2, T3, T4>(Func<T, T2, T3, T4, Task> act, TimeSpan windowTime)
    {
        T para = default;
        T2 para2 = default;
        T3 para3 = default;
        T4 para4 = default;
        var act2 = Create(() => act(para, para2, para3, para4), windowTime, false, EnumDebounceWindowMode.DelayRelativeNow);
        return async (T t, T2 t2, T3 t3, T4 t4) =>
        {
            para = t;
            para2 = t2;
            para3 = t3;
            para4 = t4;
            await act2();
        };
    }
    #endregion

    #region CreateLikeClick
    /// <summary>
    /// 类似按钮点击事件, windowTime 间隔内只执行一次(首次点击, 直接执行, 后面 windowTime 间隔内点击无效), 解释:<br /><br />
    /// 假设设定间隔为 1s, 外部触发调用的时间点为: 0s 0.4s 1.2s<br />
    /// 则最终在 0s 执行一次; 1.2s 再执行一次<br /><br/>
    /// 示例:
    /// <code>
    /// Func&lt;object, object, Task> handleClick = DebounceUtil.CreateLikeClick&lt;object, object>((object sender, object args) =>
    /// {
    ///     //处理点击事件
    ///     return Task.CompletedTask;
    /// }, TimeSpan.FromSeconds(1));
    /// </code>
    /// </summary>
    public static Func<Task> CreateLikeClick(Func<Task> act, TimeSpan windowTime)
    {
        return Create(act, windowTime, true, EnumDebounceWindowMode.Discard);
    }

    /// <inheritdoc cref="CreateLikeClick(Func{Task}, TimeSpan)" />
    public static Func<T, Task> CreateLikeClick<T>(Func<T, Task> act, TimeSpan windowTime)
    {
        T para = default;
        var act2 = Create(() => act(para), windowTime, true, EnumDebounceWindowMode.Discard);
        return async (T t) =>
        {
            para = t;
            await act2();
        };
    }

    /// <inheritdoc cref="CreateLikeClick(Func{Task}, TimeSpan)" />
    public static Func<T, T2, Task> CreateLikeClick<T, T2>(Func<T, T2, Task> act, TimeSpan windowTime)
    {
        T para = default;
        T2 para2 = default;
        var act2 = Create(() => act(para, para2), windowTime, true, EnumDebounceWindowMode.Discard);
        return async (T t, T2 t2) =>
        {
            para = t;
            para2 = t2;
            await act2();
        };
    }

    /// <inheritdoc cref="CreateLikeClick(Func{Task}, TimeSpan)" />
    public static Func<T, T2, T3, Task> CreateLikeClick<T, T2, T3>(Func<T, T2, T3, Task> act, TimeSpan windowTime)
    {
        T para = default;
        T2 para2 = default;
        T3 para3 = default;
        var act2 = Create(() => act(para, para2, para3), windowTime, true, EnumDebounceWindowMode.Discard);
        return async (T t, T2 t2, T3 t3) =>
        {
            para = t;
            para2 = t2;
            para3 = t3;
            await act2();
        };
    }

    /// <inheritdoc cref="CreateLikeClick(Func{Task}, TimeSpan)" />
    public static Func<T, T2, T3, T4, Task> CreateLikeClick<T, T2, T3, T4>(Func<T, T2, T3, T4, Task> act, TimeSpan windowTime)
    {
        T para = default;
        T2 para2 = default;
        T3 para3 = default;
        T4 para4 = default;
        var act2 = Create(() => act(para, para2, para3, para4), windowTime, true, EnumDebounceWindowMode.Discard);
        return async (T t, T2 t2, T3 t3, T4 t4) =>
        {
            para = t;
            para2 = t2;
            para3 = t3;
            para4 = t4;
            await act2();
        };
    }
    #endregion

    #region CreateLikeCache
    /// <summary>
    /// 类似触发刷新缓存, 间隔内只执行一次(首次触发, 直接执行, 后面 windowTime 间隔内触发延迟到间隔后), 解释:<br /><br />
    /// 假设设定间隔为 1s, 外部触发调用的时间点为: 0s 0.4s 0.8s 1.2s<br />
    /// 则最终在 0s 执行一次; 1s 再执行一次;  2s 又执行了一次<br /><br />
    /// 示例:
    /// <code>
    /// Func&lt;string, Task> handleClick = DebounceUtil.CreateLikeCache&lt;string>((string key) =>
    /// {
    ///     //刷新缓存数据
    ///     return Task.CompletedTask;
    /// }, TimeSpan.FromSeconds(1));
    /// </code>
    /// </summary>
    public static Func<Task> CreateLikeCache(Func<Task> act, TimeSpan windowTime)
    {
        return Create(act, windowTime, true, EnumDebounceWindowMode.DelayRelativeLastExe);
    }

    /// <inheritdoc cref="CreateLikeCache(Func{Task}, TimeSpan)"/>
    public static Func<T, Task> CreateLikeCache<T>(Func<T, Task> act, TimeSpan windowTime)
    {
        T para = default;
        var act2 = Create(() => act(para), windowTime, true, EnumDebounceWindowMode.DelayRelativeLastExe);
        return async (T t) =>
        {
            para = t;
            await act2();
        };
    }

    /// <inheritdoc cref="CreateLikeCache(Func{Task}, TimeSpan)"/>
    public static Func<T, T2, Task> CreateLikeCache<T, T2>(Func<T, T2, Task> act, TimeSpan windowTime)
    {
        T para = default;
        T2 para2 = default;
        var act2 = Create(() => act(para, para2), windowTime, true, EnumDebounceWindowMode.DelayRelativeLastExe);
        return async (T t, T2 t2) =>
        {
            para = t;
            para2 = t2;
            await act2();
        };
    }

    /// <inheritdoc cref="CreateLikeCache(Func{Task}, TimeSpan)"/>
    public static Func<T, T2, T3, Task> CreateLikeCache<T, T2, T3>(Func<T, T2, T3, Task> act, TimeSpan windowTime)
    {
        T para = default;
        T2 para2 = default;
        T3 para3 = default;
        var act2 = Create(() => act(para, para2, para3), windowTime, true, EnumDebounceWindowMode.DelayRelativeLastExe);
        return async (T t, T2 t2, T3 t3) =>
        {
            para = t;
            para2 = t2;
            para3 = t3;
            await act2();
        };
    }

    /// <inheritdoc cref="CreateLikeCache(Func{Task}, TimeSpan)"/>
    public static Func<T, T2, T3, T4, Task> CreateLikeCache<T, T2, T3, T4>(Func<T, T2, T3, T4, Task> act, TimeSpan windowTime)
    {
        T para = default;
        T2 para2 = default;
        T3 para3 = default;
        T4 para4 = default;
        var act2 = Create(() => act(para, para2, para3, para4), windowTime, true, EnumDebounceWindowMode.DelayRelativeLastExe);
        return async (T t, T2 t2, T3 t3, T4 t4) =>
        {
            para = t;
            para2 = t2;
            para3 = t3;
            para4 = t4;
            await act2();
        };
    }
    #endregion
}
